/*
	File:		Graphics_Win32.h
	Author:		dmonroe
	Date:		10/14/2012
	Copyright:	Full Sail University

	Purpose:	Win32 graphics implementation using OpenGL
*/


// Only compile on Win32
#if defined( _WIN32 )

#include "../source/IGraphics.h"

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WindowsX.h>

#include <gl/GL.h>
#include <gl/GLU.h>
#include <exception>

#pragma comment( lib, "OpenGL32.lib" )
#pragma comment( lib, "GLU32.lib" )

#include <assert.h>

#include <stdlib.h>
#include "../source_Win32/SOIL.h"
#pragma comment( lib, "source_Win32/SOIL.lib" )


#include "../source/Constants.h"
#include "../source/SGD_HandleManager.h"
//using namespace SGD;

#include "../source/Geometry.h"
#include "../source_Win32/KeyboardAndMouse.h"


//******************************************************************
// CGraphics
//	- concrete class for handling graphics on Win32
//	- creates / handles window
//	- loads / draws 2d textures
class CGraphics : public IGraphics
{
public:
	// SINGLETON helper:
	friend	IGraphics*	IGraphics::GetInstance		( void );
	friend	void		IGraphics::DeleteInstance	( void );


	// SETUP
	virtual	bool		Initialize		( const char* title, unsigned short& width, unsigned short& height );
	virtual	bool		Terminate		( void );
	
	// FRAME UPDATE
	virtual bool		Update			( void );
	virtual bool		BeginScene		( void );
	virtual bool		EndScene		( void );

	// 2D
	virtual HTexture	LoadTexture			( const char* filename );
	virtual bool		UnloadTexture		( HTexture handle );

	virtual bool		DrawTexture			( HTexture handle,			int x, int y, 
											  float rotation = 0.0f,	float rotationPointX = 0.0f,	float rotationPointY = 0.0f, 
											  SColor color = SColor(),	float scaleX = 1.0f,			float scaleY = 1.0f );
	
	virtual bool		DrawTextureSection	( HTexture handle,			int x, int y,					SRect* section,
											  float rotation = 0.0f,	float rotationPointX = 0.0f,	float rotationPointY = 0.0f, 
											  SColor color = SColor(),	float scaleX = 1.0f,			float scaleY = 1.0f );
	
	virtual bool		DrawRectangle		( SRect* rectangle,
											  float rotation = 0.0f,	float rotationPointX = 0.0f,	float rotationPointY = 0.0f, 
											  SColor color = SColor(),	float scaleX = 1.0f,			float scaleY = 1.0f );

	virtual bool		DrawLine			( int x1, int y1,			int x2, int y2,
											  SColor color = SColor(),	float width = 1.0f,				bool smooth = true );

	// WINDOWS MESSAGE INPUT
	SKeyboardState		GetKeyboardState( void ) const	{	return m_KeyboardState;	}
	SMouseState			GetMouseState	( void ) const	{	return m_MouseState;	}

private:
	// SINGLETON
	static CGraphics*	s_Instance;

	CGraphics				( void );					// Default constructor
	virtual	~CGraphics		( void );					// Destructor

	CGraphics				( const CGraphics& );		// Copy constructor
	CGraphics&	operator=	( const CGraphics& );		// Assignment operator


	// WINDOW HANDLERS
	bool		RegisterWindowClass		( void );
	HWND		MakeWindow				( const char* szTitle, int width, int height, bool windowed );
	HGLRC		MakeGLContext			( HDC hDC );
	bool		InitGL					( void );
	LRESULT		WindowProc				( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );

	static LRESULT CALLBACK	WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );


	
	// Wrapper Status
	enum EWrapperStatus
	{	
		E_UNINITIALIZED,
		E_INITIALIZED,
		E_DESTROYED
	};

	EWrapperStatus			m_eStatus;					// wrapper initialization status

	// Window properties
	HWND					m_hWnd;
	HINSTANCE				m_hInstance;
	unsigned short			m_usScreenWidth;
	unsigned short			m_usScreenHeight;
	char					m_szTitle[ 256 ];

	// OpenGL rendering
	HDC						m_hDC;						// graphics device context (window DC)
	HGLRC					m_hGLRC;					// opengl render context (attached to hdc)
	
	// Window message input
	SKeyboardState			m_KeyboardState;
	SMouseState				m_MouseState;


	// Data storage
	struct STextureInfo
	{
		char*				szName;
		GLuint				nTextureID;
		int					nWidth;
		int					nHeight;
		unsigned int		unRefCount;
	};
	
	CHandleManager< STextureInfo >	m_HandleManager;
	
	
	// TEXTURE REFERENCE HELPER METHOD
	struct SSearchInfo
	{
		const char*		filename;	// input
		STextureInfo*	texture;	// output
		HTexture		handle;		// output
	};
	static bool FindTextureByName( CHandle handle, STextureInfo& data, SSearchInfo* extra );
};
//******************************************************************




//******************************************************************
// SINGLETON

// Instantiate static pointer to null (no instance yet)
/*static*/ CGraphics*	CGraphics::s_Instance		= NULL;

// Interface singleton accessor
/*static*/ IGraphics*	IGraphics::GetInstance		( void )
{
	// Allocate singleton on first use
	if( CGraphics::s_Instance == NULL )
		CGraphics::s_Instance = new CGraphics;

	// Return the singleton
	return CGraphics::s_Instance;
}

// Interface singleton destructor
/*static*/ void			IGraphics::DeleteInstance	( void )
{
	// Deallocate singleton
	delete CGraphics::s_Instance;
	CGraphics::s_Instance = NULL;
}
//******************************************************************



//******************************************************************
// CONSTRUCTOR
CGraphics::CGraphics( void )
{
	// Set data members to 'clean' values
	m_eStatus = E_UNINITIALIZED;

	m_hWnd				= 0;
	m_hInstance			= 0;
	m_usScreenWidth		= 0;
	m_usScreenHeight	= 0;
	m_szTitle[ 0 ]		= 0;

	m_hDC				= 0;	
	m_hGLRC				= 0;

	m_KeyboardState		= SKeyboardState();
	m_MouseState		= SMouseState();

	(void)m_HandleManager;		// default initialized already
}
//******************************************************************



//******************************************************************
// DESTRUCTOR
/*virtual*/	CGraphics::~CGraphics( void )
{
	// Release memory cleanly in Release mode
#if !defined( DEBUG ) && !defined( _DEBUG )
	if( m_eStatus == E_INITIALIZED )
		Terminate();
#endif
}
//******************************************************************




//******************************************************************
// INITIALIZE
/*virtual*/	bool CGraphics::Initialize( const char* title, unsigned short& width, unsigned short& height )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_UNINITIALIZED && "CGraphics::Initialize - wrapper has already been initialized" );

	// Check the parameters
	assert( title != NULL && strlen( title ) != 0 && "CGraphics::Initialize - title parameter must have a value" );
	assert( strlen( title ) < 256 && "CGraphics::Initialize - title parameter must be less than 255 characters" );
	

	// Store the parameters
	strcpy_s( m_szTitle, 256, title );
	m_usScreenWidth		= width;
	m_usScreenHeight	= height;
	

	// Retrieve the application instance
	HWND hConsoleWnd = GetConsoleWindow();
	m_hInstance = (HINSTANCE)GetWindowLong( hConsoleWnd, GWL_HINSTANCE );
	
	//m_hInstance = (HINSTANCE)GetModuleHandle( NULL );


	// Register the window class
	bool result = RegisterWindowClass();
	if( result == false )
	{
		// MESSAGE
		return false;
	}

	// Create the window
#if defined( _DEBUG ) || defined( DEBUG )
	m_hWnd = CGraphics::MakeWindow( m_szTitle, m_usScreenWidth, m_usScreenHeight, true );
#else
	m_hWnd = CGraphics::MakeWindow( m_szTitle, m_usScreenWidth, m_usScreenHeight, false );
#endif
	
	if( m_hWnd == 0 )
	{
		// MESSAGE

		// Unregister the window class
		UnregisterClass( m_szTitle, m_hInstance );
		return false;
	}
	
	// Update the parameters with a potentially new size
	width  = m_usScreenWidth;
	height = m_usScreenHeight;


	// Display the window
	ShowWindow( m_hWnd, SW_SHOWNORMAL );
	UpdateWindow( m_hWnd );


	// Get the window's Device Context
	m_hDC = GetDC( m_hWnd );
	if( m_hDC == 0 )
	{
		// MESSAGE

		// Destroy the window
		DestroyWindow( m_hWnd );
		
		// Unregister the window class
		UnregisterClass( m_szTitle, m_hInstance );
		return false;
	}

	// Make a Rendering Context for OpenGL
	m_hGLRC = MakeGLContext( m_hDC );
	if( m_hGLRC == 0 )
	{
		// MESSAGE

		// Release the Device Context
		ReleaseDC( m_hWnd, m_hDC );

		// Destroy the window
		DestroyWindow( m_hWnd );
		
		// Unregister the window class
		UnregisterClass( m_szTitle, m_hInstance );
		return false;
	}

	// Bind the Rendering Context to the Device Context
	// (each thread requires a separate rendering context)
	if( wglMakeCurrent( m_hDC, m_hGLRC ) == FALSE )
	{
		// MESSAGE

		// Delete the Rendering Context
		wglDeleteContext( m_hGLRC );

		// Release the Device Context
		ReleaseDC( m_hWnd, m_hDC );

		// Destroy the window
		DestroyWindow( m_hWnd );
		
		// Unregister the window class
		UnregisterClass( m_szTitle, m_hInstance );
		return false;
	}

	
	// Initialize OpenGL
	if( InitGL() == false )
	{
		// MESSAGE

		// Delete the Rendering Context
		wglDeleteContext( m_hGLRC );

		// Release the Device Context
		ReleaseDC( m_hWnd, m_hDC );

		// Destroy the window
		DestroyWindow( m_hWnd );
		
		// Unregister the window class
		UnregisterClass( m_szTitle, m_hInstance );
		return false;
	}


	// Success!
	m_eStatus = E_INITIALIZED;
	return true;
}
//******************************************************************



//******************************************************************
// TERMINATE
/*virtual*/	bool CGraphics::Terminate( void )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CGraphics::Terminate - wrapper has not been initialized" );

	// Unbind the Rendering Context
	wglMakeCurrent( m_hDC, 0 );

	// Delete the Rendering Context
	wglDeleteContext( m_hGLRC );

	// Release the Device Context
	ReleaseDC( m_hWnd, m_hDC );

	// Destroy the window
	DestroyWindow( m_hWnd );

	// Unregister the window class
	UnregisterClass( m_szTitle, m_hInstance );

	// Wrapper is now destroyed
	m_eStatus = E_DESTROYED;
	return true;
}
//******************************************************************



//******************************************************************
// UPDATE
//	- process window's messages
/*virtual*/ bool CGraphics::Update( void )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CGraphics::Update - wrapper has not been initialized" );
	
	// Reset the keyboard & mouse states
	//m_KeyboardState = SKeyboardState();
	//m_MouseState	= SMouseState();
	m_MouseState.nDeltaX = 0;
	m_MouseState.nDeltaY = 0;


	MSG msg;

	// Loop through all the window's messages
	while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) == TRUE )
	{ 
		// Close application on WM_QUIT
		if( msg.message == WM_QUIT )
			return false;
		
		// Translate any accelerator keys
		TranslateMessage( &msg );

		// Send the message to the window proc
		DispatchMessage( &msg );
	}
	
	// Continue to run the application
	return true;
}
//******************************************************************



//******************************************************************
// BEGIN SCENE
/*virtual*/ bool CGraphics::BeginScene( void )
{
	// Clear the buffer
	glClear( GL_COLOR_BUFFER_BIT );

	return true;
}	
//******************************************************************


	
//******************************************************************
// END SCENE
/*virtual*/ bool CGraphics::EndScene( void )
{
	// Swap buffers
	SwapBuffers( m_hDC );

	return true;
}	
//******************************************************************



//******************************************************************
// LOAD TEXTURE
/*virtual*/ HTexture CGraphics::LoadTexture( const char* filename )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CGraphics::LoadTexture - wrapper has not been initialized" );
	assert( filename != NULL && "CGraphics::LoadTexture - invalid filename" );
	
	
	// Attempt to find the texture in the Handle Manager
	SSearchInfo search = { filename, NULL, HTexture() };
	m_HandleManager.ForEach( &CGraphics::FindTextureByName, &search );

	// If it was found, increase the reference & return the existing handle
	if( search.texture != NULL )
	{
		search.texture->unRefCount++;
		return search.handle;
	}


	// Load a new texture
	STextureInfo info = { };

	info.nTextureID = SOIL_load_OGL_texture( filename, SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_POWER_OF_TWO | SOIL_FLAG_MIPMAPS );
	
	// Did it fail?
	if( info.nTextureID == 0 )
	{
		// MESSAGE
		return HTexture( /*invalid*/ );
	}

	// Set remaining data members
	info.szName = _strdup( filename );
	info.unRefCount	= 1;

	glBindTexture( GL_TEXTURE_2D, info.nTextureID );
	glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH,  &info.nWidth  );
	glGetTexLevelParameteriv( GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &info.nHeight );
	glBindTexture( GL_TEXTURE_2D, 0 );

	// Store the texture in the Handle Manager
	return m_HandleManager.StoreData( info );
}
//******************************************************************



//******************************************************************
// UNLOAD TEXTURE
/*virtual*/ bool CGraphics::UnloadTexture( HTexture handle )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CGraphics::UnloadTexture - wrapper has not been initialized" );
	assert( handle != INVALID_HANDLE && "CGraphics::UnloadTexture - invalid handle" );


	// Get the texture info from the Handle Manager
	STextureInfo& info = m_HandleManager.GetData( handle );

	// Release a reference
	info.unRefCount--;

	
	// Are there other references?
	if( info.unRefCount > 0 )
		return false;


	// Remove the texture info from the Handle Manager
	STextureInfo old = m_HandleManager.RemoveData( handle );

	// Remove the texture from the OpenGL buffer
	glDeleteTextures( 1, &old.nTextureID );

	// Deallocate the texture's name
	delete[] old.szName;

	return true;
}
//******************************************************************



//******************************************************************
// DRAW TEXTURE
/*virtual*/ bool CGraphics::DrawTexture( HTexture handle,	int x, int y, 
										 float rotation,	float rotationPointX,	float rotationPointY, 
										 SColor color,		float scaleX,			float scaleY )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CGraphics::DrawTexture - wrapper has not been initialized" );


	// Get the texture info from the Handle Manager
	STextureInfo& info = m_HandleManager.GetData( handle );

	// Bind the texture to OpenGL
	glEnable( GL_TEXTURE_2D );
	glBindTexture( GL_TEXTURE_2D, info.nTextureID );

	// Color
	glColor4fv( (const GLfloat*)color.components );

	
	// Retain original matrix
	glPushMatrix();
	{
		// Move to position
		glTranslatef( (GLfloat)x, (GLfloat)y, 0.0f );
		
		// Scale
		glScalef	( scaleX, scaleY, 1 );

		// Offset and rotate
		glTranslatef( +rotationPointX, +rotationPointY, 0.0f );
		glRotatef	( rotation, 0.0f, 0.0f, 1.0f );
		glTranslatef( -rotationPointX, -rotationPointY, 0.0f );


		// UV Coordinates:				World Coordinates:
		//	(0,0)		(1,0)			(0,0)		(width,0)
		//		+-------+					+-------+	
		//		|		|					|		|	
		//		|		|					|		|	
		//		+-------+					+-------+	
		//	(0,1)		(1,1)			(0,height)	(width,height)
		
		float quad[] = 
		{ 
			0.0f,			0.0f,			0.0f,		// top left
			info.nWidth,	0.0f,			0.0f,		// top right
			info.nWidth,	info.nHeight,	0.0f,		// bottom right
			0.0f,			info.nHeight,	0.0f,		// bottom left
		};

		float uv[] =					
		{								
			0.0f, 0.0f,									// top left
			1.0f, 0.0f,									// top right
			1.0f, 1.0f,									// bottom right
			0.0f, 1.0f,									// bottom left
		};

		
		glEnableClientState( GL_VERTEX_ARRAY );
		glVertexPointer( 3, GL_FLOAT, 0, quad );
	
		glEnableClientState( GL_TEXTURE_COORD_ARRAY );
		glTexCoordPointer( 2, GL_FLOAT, 0, uv );
		
		glDrawArrays( GL_QUADS, 0, 4 );
	}
	glPopMatrix();


	// Unbind the texture
	glBindTexture( GL_TEXTURE_2D, 0 );

	return true;
}
//******************************************************************



//******************************************************************
// DRAW TEXTURE SECTION
/*virtual*/ bool CGraphics::DrawTextureSection( HTexture handle,	int x, int y,			SRect* section,
												float rotation,		float rotationPointX,	float rotationPointY, 
												SColor color,		float scaleX,			float scaleY )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CGraphics::DrawTextureSection - wrapper has not been initialized" );

	// Use other DrawTexture method if the section is null ...
	if( section == NULL )
		return DrawTexture( handle, x, y, rotation, rotationPointX, rotationPointY, color, scaleX, scaleY );


	// Verify the rect dimensions
	int width  = section->right  - section->left;
	int height = section->bottom - section->top;
	assert( width > 0 && height > 0 && "CGraphics::DrawTextureSection - invalid rect" );



	// Get the texture info from the Handle Manager
	STextureInfo& info = m_HandleManager.GetData( handle );

	// Bind the texture to OpenGL
	glEnable( GL_TEXTURE_2D );
	glBindTexture( GL_TEXTURE_2D, info.nTextureID );

	// Color
	glColor4fv( (const GLfloat*)color.components );
	

	// Retain original matrix
	glPushMatrix();
	{
		// Move to position
		glTranslatef( (GLfloat)x, (GLfloat)y, 0.0f );
		
		// Scale
		glScalef	( scaleX, scaleY, 1 );

		// Offset and rotate
		glTranslatef( +rotationPointX, +rotationPointY, 0.0f );
		glRotatef	( rotation, 0.0f, 0.0f, 1.0f );
		glTranslatef( -rotationPointX, -rotationPointY, 0.0f );


		// UV Coordinates:				World Coordinates:
		//	(0,0)		(1,0)			(0,0)		(width,0)
		//		+-------+					+-------+	
		//		|		|					|		|	
		//		|		|					|		|	
		//		+-------+					+-------+	
		//	(0,1)		(1,1)			(0,height)	(width,height)
		
		// Use the provided SRect section
		float quad[] = 
		{ 
			0.0f,		0.0f,		0.0f,		// top left
			width,		0.0f,		0.0f,		// top right
			width,		height,		0.0f,		// bottom right
			0.0f,		height,		0.0f,		// bottom left
		};
		
		// Map the provided SRect section to UV coordinates
		float uScale = 1.0f / info.nWidth;
		float fStartU = section->left * uScale;
		float fEndU = section->right * uScale;

		float vScale = 1.0f / info.nHeight;
		float fStartV = section->top * vScale;
		float fEndV = section->bottom * uScale;

		float uv[] =					
		{								
			fStartU,	fStartV,				// top left
			fEndU,		fStartV,				// top right
			fEndU,		fEndV,					// bottom right
			fStartU,	fEndV,					// bottom left
		};
		
		
		glEnableClientState( GL_VERTEX_ARRAY );
		glVertexPointer( 3, GL_FLOAT, 0, quad );
	
		glEnableClientState( GL_TEXTURE_COORD_ARRAY );
		glTexCoordPointer( 2, GL_FLOAT, 0, uv );
		
		glDrawArrays( GL_QUADS, 0, 4 );
	}
	glPopMatrix();


	// Unbind the texture
	glBindTexture( GL_TEXTURE_2D, 0 );

	return true;
}
//******************************************************************



//******************************************************************
// DRAW RECTANGLE
/*virtual*/ bool CGraphics::DrawRectangle( SRect* rectangle,
										   float rotation,	float rotationPointX,	float rotationPointY, 
										   SColor color,	float scaleX,			float scaleY )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CGraphics::DrawRectangle - wrapper has not been initialized" );

	// Verify the rectangle
	assert( rectangle != NULL && "CGraphics::DrawRectangle - invalid rect" );

	int width  = rectangle->right  - rectangle->left;
	int height = rectangle->bottom - rectangle->top;
	assert( width > 0 && height > 0 && "CGraphics::DrawRectangle - invalid rect" );

	int x = rectangle->left;
	int y = rectangle->top;
	
	// Color
	glColor4fv( (const GLfloat*)color.components );

	// Retain original matrix
	glPushMatrix();
	{

		// Move to position
		glTranslatef( (GLfloat)x, (GLfloat)y, 0.0f );
		
		// Scale
		glScalef	( scaleX, scaleY, 1 );

		// Offset and rotate
		glTranslatef( +rotationPointX, +rotationPointY, 0.0f );
		glRotatef	( rotation, 0.0f, 0.0f, 1.0f );
		glTranslatef( -rotationPointX, -rotationPointY, 0.0f );


		// World Coordinates:
		//	(0,0)		(width,0)
		//		+-------+	
		//		|		|	
		//		|		|	
		//		+-------+	
		//	(0,height)	(width,height)
		
		// Use the provided SRect section
		float quad[] = 
		{ 
			0.0f,		0.0f,		0.0f,		// top left
			width,		0.0f,		0.0f,		// top right
			width,		height,		0.0f,		// bottom right
			0.0f,		height,		0.0f,		// bottom left
		};
				
		glEnableClientState( GL_VERTEX_ARRAY );
		glVertexPointer( 3, GL_FLOAT, 0, quad );
	
		glDrawArrays( GL_QUADS, 0, 4 );
	}
	glPopMatrix();

	return true;
}
//******************************************************************



//******************************************************************
// DRAW LINE
/*virtual*/ bool CGraphics::DrawLine( int x1, int y1,	int x2, int y2,
									  SColor color,		float width,		bool smooth )
{
	// Sanity-check the wrapper's status
	assert( m_eStatus == E_INITIALIZED && "CGraphics::DrawRectangle - wrapper has not been initialized" );


	// Anti-aliased lines?
	if( smooth == true )
		glEnable( GL_LINE_SMOOTH );

	// Set line width
	glLineWidth( (GLfloat)width );

	// Color
	glColor4fv( (const GLfloat*)color.components );

	// Retain original matrix
	glPushMatrix();
	{
		float line[] = 
		{
			x1, y1, 0.0f,
			x2, y2, 0.0f,
		};
		
		glEnableClientState( GL_VERTEX_ARRAY );
		glVertexPointer( 3, GL_FLOAT, 0, line );

		glDrawArrays( GL_LINE_STRIP, 0, 2 );
	}
	glPopMatrix();



	if( smooth == true )
		glDisable( GL_LINE_SMOOTH );


	return false;
}
//******************************************************************



//******************************************************************
// REGISTER WINDOW CLASS
bool CGraphics::RegisterWindowClass( void )
{
	WNDCLASSEX	winClassEx;	//	This will describe the window class we will create.

	//	First fill in the window class structure
	winClassEx.cbSize			= sizeof(winClassEx);
	winClassEx.style			= CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	winClassEx.lpfnWndProc		= &CGraphics::WndProc;
	winClassEx.cbClsExtra		= 0;
	winClassEx.cbWndExtra		= 0;
	winClassEx.hInstance		= m_hInstance;
	winClassEx.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
	winClassEx.hIconSm			= NULL;
	winClassEx.hCursor			= LoadCursor(NULL, IDC_ARROW);
	winClassEx.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
	winClassEx.lpszMenuName		= NULL; 
	winClassEx.lpszClassName	= m_szTitle;

	//	Register the window class
	return (RegisterClassEx( &winClassEx ) != FALSE);
}
//******************************************************************



//******************************************************************
// MAKE WINDOW
HWND CGraphics::MakeWindow( const char* szTitle, int width, int height, bool windowed )
{
	// Setup window style flags
	DWORD dwWindowStyleFlags = WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

	// Windowed?
	if( windowed == true )
		dwWindowStyleFlags |= WS_OVERLAPPEDWINDOW;
	else
	{
		dwWindowStyleFlags |= WS_POPUP;
		ShowCursor( FALSE );				// Stop showing the mouse cursor
	}

	// Setup the desired client area size
	RECT rWindow;
	rWindow.left	= 0;
	rWindow.top		= 0;
	rWindow.right	= (LONG)width;
	rWindow.bottom	= (LONG)height;

	// Get the dimensions of a window that will have a client rect that
	// will really be the resolution we're looking for.
	AdjustWindowRectEx( &rWindow, 
						dwWindowStyleFlags,
						FALSE, 
						WS_EX_APPWINDOW );
	
	// Calculate the width/height of the window's dimensions
	int nWindowWidth	= rWindow.right - rWindow.left;
	int nWindowHeight	= rWindow.bottom - rWindow.top;

	//	Create the window
	return CreateWindowEx(
			WS_EX_APPWINDOW,										//	Extended Style flags.
			m_szTitle,												//	Window Class Name.
			szTitle,												//	Title of the Window.
			dwWindowStyleFlags,										//	Window Style Flags.
			(GetSystemMetrics(SM_CXSCREEN) - nWindowWidth)/2,		//	Window Start Point (x, y). 
			(GetSystemMetrics(SM_CYSCREEN) - nWindowHeight)/2,		//		-Does the math to center the window over the desktop.
			nWindowWidth,											//	Width of Window.
			nWindowHeight,											//	Height of Window.
			HWND_DESKTOP,											//	Handle to parent window.
			NULL,													//	Handle to menu.
			m_hInstance,											//	Application Instance.
			NULL);													//	Creation parameters.
}
//******************************************************************



//******************************************************************
// WINDOW PROC
LRESULT CGraphics::WindowProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	// Which window is this? (Could be a child window)
	if( hWnd != m_hWnd )
		return DefWindowProc( hWnd, msg, wParam, lParam );

	// NOTE:
	//	- check that the wrapper completed its initialization
	//	  before accessing it!
	
	//	What is the message?
	switch( msg )
	{
	
	//	Window constructor
	case WM_CREATE: 
		{
			//	Window initialization here
		}
		break;
	
	//	Window destructor
	case WM_DESTROY: 
		{
			//	Kill the application			
			PostQuitMessage( 0 );
		}
		break;


	//	Ignore ALT-space pop up menu (system menu)
	case WM_SYSKEYUP:
	case WM_SYSCHAR:
		{
		}
		break;

	//	Handle ALT+F4
	case WM_CLOSE:
		{
			// Send a WM_DESTROY message to the window
			DestroyWindow(hWnd);			
		}
		break;

	//	Gain / lose focus
	case WM_ACTIVATE:
		{
			//if( LOWORD( wParam ) != WA_INACTIVE )	//	gaining focus
			//{
				// Unpause game here
			//}
			//else									//	losing focus
			//{
				// Pause game here
			//}
		}
		break;

	case WM_PAINT:
		{
			ValidateRect( hWnd, NULL );


			//PAINTSTRUCT	ps;
			//HDC			hdc;
			
			// Start painting
			//hdc = BeginPaint( hWnd, &ps );

			// End painting
			//EndPaint( hWnd, &ps );
		}
		break;

	case WM_KEYDOWN:
		{
			//wParam
			//    The virtual-key code of the nonsystem key. See Virtual-Key Codes.
			//
			//lParam
			//    The repeat count, scan code, extended-key flag, context code, previous key-state flag, and transition-state flag, as shown following.
			//
			if( wParam < 256 )
				m_KeyboardState.bKeys[ wParam ] = true;

			switch( wParam )		// not actually useful ?!?
			{
			case VK_LBUTTON:
				m_MouseState.bButtons[ 0 ] = true;
				break;

			case VK_RBUTTON:
				m_MouseState.bButtons[ 1 ] = true;
				break;

			case VK_MBUTTON:
				m_MouseState.bButtons[ 2 ] = true;
				break;

			case VK_XBUTTON1:
				m_MouseState.bButtons[ 3 ] = true;
				break;

			case VK_XBUTTON2:
				m_MouseState.bButtons[ 4 ] = true;
				break;
			}
		}
		break;
	
	case WM_KEYUP:
		{
			//wParam
			//    The virtual-key code of the nonsystem key. See Virtual-Key Codes.
			//
			//lParam
			//    The repeat count, scan code, extended-key flag, context code, previous key-state flag, and transition-state flag, as shown in the following table. 
			//
			if( wParam < 256 )
				m_KeyboardState.bKeys[ wParam ] = false;
			
			switch( wParam )
			{
			case VK_LBUTTON:
				m_MouseState.bButtons[ 0 ] = false;
				break;

			case VK_RBUTTON:
				m_MouseState.bButtons[ 1 ] = false;
				break;

			case VK_MBUTTON:
				m_MouseState.bButtons[ 2 ] = false;
				break;

			case VK_XBUTTON1:
				m_MouseState.bButtons[ 3 ] = false;
				break;

			case VK_XBUTTON2:
				m_MouseState.bButtons[ 4 ] = false;
				break;
			}
		}
		break;

	case WM_MOUSEMOVE:
		{
			// Calculate relative mouse movement
			m_MouseState.nDeltaX = GET_X_LPARAM( lParam ) - m_MouseState.nXPos;
			m_MouseState.nDeltaY = GET_Y_LPARAM( lParam ) - m_MouseState.nYPos;

			// Store mouse position
			m_MouseState.nXPos = GET_X_LPARAM( lParam );
			m_MouseState.nYPos = GET_Y_LPARAM( lParam );

			// Store mouse buttons?
		}
		break;

	case WM_LBUTTONDOWN:
		{
			m_MouseState.bButtons[ 0 ] = true;
			m_KeyboardState.bKeys[ VK_LBUTTON ] = true;
		}
		break;

	case WM_LBUTTONUP:
		{
			m_MouseState.bButtons[ 0 ] = false;
			m_KeyboardState.bKeys[ VK_LBUTTON ] = false;
		}
		break;

	case WM_RBUTTONDOWN:
		{
			m_MouseState.bButtons[ 1 ] = true;
			m_KeyboardState.bKeys[ VK_RBUTTON ] = true;
		}
		break;
		
	case WM_RBUTTONUP:
		{
			m_MouseState.bButtons[ 1 ] = false;
			m_KeyboardState.bKeys[ VK_RBUTTON ] = false;
		}
		break;

	
	default:
		//	Process any messages that we didn't take care of 
		return DefWindowProc( hWnd, msg, wParam, lParam );
	}

	//	We have handled this message.
	return 0;
}
//******************************************************************



//******************************************************************
// WIN PROC
//	- static callback for the window
/*static*/ LRESULT CALLBACK CGraphics::WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	// Access the singleton
	CGraphics* self = CGraphics::s_Instance;
	assert( self != NULL && "CGraphics::WndProc - CGraphics has not been instantiated" );
	
	// Use the wrapper's proc 
	return self->WindowProc( hWnd, msg, wParam, lParam );
}
//******************************************************************



//******************************************************************
// MAKE GL CONTEXT
HGLRC CGraphics::MakeGLContext( HDC hDC )
{
	// Describe the desired pixel format for the Device Context
	PIXELFORMATDESCRIPTOR pfd;
	ZeroMemory( &pfd, sizeof( pfd ) );

	pfd.nSize		= sizeof( pfd );			// size of the structure
	pfd.nVersion	= 1;						// version number
	pfd.dwFlags		= PFD_DRAW_TO_WINDOW		// render to window
					  | PFD_SUPPORT_OPENGL		// support Open GL calls
					  | PFD_DOUBLEBUFFER;		// enable back buffer
	pfd.iPixelType	= PFD_TYPE_RGBA;			// pixel color: RGBA
	pfd.cColorBits	= 24;						// 24-bit color depth
	pfd.cDepthBits	= 32;						// 32-bit z-buffer
	pfd.dwLayerMask	= PFD_MAIN_PLANE;			// main layer

	// Choose the closest pixel format to our request
	int format = ChoosePixelFormat( hDC, &pfd );

	// Set the pixel format for the Device Context
	SetPixelFormat( hDC, format, &pfd );


	// Create a Rendering Context for Open GL
	return wglCreateContext( hDC );
}
//******************************************************************



//******************************************************************
//	INIT GL
bool CGraphics::InitGL( void )
{
	// SOURCE:
	// http://www.codeproject.com/Articles/27219/TetroGL-An-OpenGL-Game-Tutorial-in-C-for-Win32-Pla#Modelingtransformations11


	// Enable 2D texturing
	glEnable(GL_TEXTURE_2D);

	// Choose a smooth shading model
	glShadeModel(GL_SMOOTH);


	// Set clear color
	glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
	
	
	// Enable blending for primitives
	glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

	// Enable the alpha test. This is needed 
	// to be able to have images with transparent 
	// parts.
	glEnable( GL_ALPHA_TEST );
	glAlphaFunc( GL_GREATER, 0.0f );
	
	
	// Sets the size of the OpenGL viewport
	//glViewport(0,0,m_usScreenWidth,m_usScreenHeight);

	// Select the projection stack and apply
	// an orthographic projection
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0.0,m_usScreenWidth,m_usScreenHeight,0.0,-1.0,1.0);
	glMatrixMode(GL_MODELVIEW);
	return true;
}
//******************************************************************


//******************************************************************
// FIND TEXTURE BY NAME
//	- callback function for the Handle Manager
//	- returns false to stop searching
/*static*/ bool CGraphics::FindTextureByName( CHandle handle, STextureInfo& data, SSearchInfo* extra )
{
	// Compare the names
	if( strcmp( data.szName, extra->filename ) == 0 )
	{
		// Texture does exist!
		extra->texture	= &data;
		extra->handle	= handle;
		return false;
	}

	// Did not find yet
	return true;
}
//******************************************************************



//******************************************************************
// GET KEYBOARD STATE
//	- global function to get the keyboard state from windows messages
//	- parameter will store the output
void GetKeyboardState( SKeyboardState* state )
{
	// Validate the parameter
	assert( state != NULL && "GetKeyboardState - parameter cannot be null" );

	*state = static_cast<CGraphics*>( IGraphics::GetInstance() )->GetKeyboardState();
}
//******************************************************************



//******************************************************************
// GET MOUSE STATE
//	- global function to get the mouse state from windows messages
//	- parameter will store the output
void GetMouseState( SMouseState* state )
{
	// Validate the parameter
	assert( state != NULL && "GetMouseState - parameter cannot be null" );

	*state = static_cast<CGraphics*>( IGraphics::GetInstance() )->GetMouseState();
}
//******************************************************************



#endif	// defined( _WIN32 )
